package ZipSlurp;
use strict;
use warnings;

use feature qw/say/;
use Cwd                     qw{abs_path    cwd};           
use File::Basename;                                        
use IO::Uncompress::Bunzip2 qw(bunzip2     $Bunzip2Error); # The zip/unzip utils
use IO::Uncompress::Gunzip  qw(gunzip      $GunzipError);  # 
use IO::Compress::Gzip qw(gzip $GzipError) ;               # 
use IO::Compress::Bzip2 qw(bzip2 $Bzip2Error) ;            # 

use Path::Tiny;  # provide path() and slurp_raw() functions; safer than File::Slurp;

use Carp::Always::Color;                                                                
use CON;                 # exports CON(), and CONm($str, 4); to highlight strings tweaked by number
use FlHandle qw/GetYWRndStr/;
use DDP;

use Exporter;
use vars qw(@ISA @EXPORT);
push @ISA,    qw(Exporter);
push @EXPORT, qw(
  read_text_zipped_okay
  *readZF
  write_file_zip
  writeZF
);

$|=1;

sub read_text_zipped_okay
{#_{{_
    my ( $fi, $rstr_back, $rMainWarningStr ) = @_;
    if ( not $fi ) {
        Carp::cluck "-W- no file provided";
        if ($rMainWarningStr) {
            $$rMainWarningStr .= sprintf "%s no file provided;\n", &CON( q/-W-/, q/red/ );
        }
        return;
    }
    my $absF;
    if ( $fi and -e $fi ) {
        $absF = abs_path($fi);
    } else {
        Carp::cluck "-W- cannot see $fi";
        if ($rMainWarningStr) {
            $$rMainWarningStr .= sprintf "%s cannot see %s ;\n", &CON( q/-W-/, q/red/ ), &CON( $fi, q/red/ );
        }
        return;
    }

    my ( $tfbase, $tDir, $suffix ) = fileparse( $absF, qr{\.[^.]*(\.(?:gz|bz2))?} );
    my $fb1 = path($absF);
    my $str1= $fb1 ->slurp_raw();

    if ( $suffix =~ /\.gz/i ) { &gunzip( \$str1 => $rstr_back ) or Carp::confess "-E-cannot unzip strings in $absF; $GunzipError"; }
    elsif ( $suffix =~ /\.bz2/i ) { &bunzip2( \$str1 => $rstr_back ) or Carp::confess "-E-cannot unzip strings in $absF; $Bunzip2Error"; }
    else                         { $$rstr_back = $str1; }

}#_}}_

sub write_file_zip 
{#_{{_
    my ( $rStr, $fname ) = @_;
    my $rStr0;
    if ( defined $rStr ) {
        my $refrStr = ref $rStr;
        if ( $refrStr eq q/SCALAR/ ) {
            $rStr0 = $rStr;
        } elsif ( $refrStr !~ m/\w/ ) {
            $rStr0 = \$rStr;    # passed in a string SCALAR
        } else {
            $$rStr0 = q//;
        }
    } else {
        $$rStr0 = q//;
    }

    if ($fname) {
    } else {
        $fname = &GetYWRndStr;
        $fname .= qq/.txt.bz2/;
    }
    my ( $tfbase, $tDir, $suffix ) = fileparse( $fname, qr{\.[^.]*(\.(?:gz|bz2))?} );
    my $rstrOut;
    if ( $suffix =~ /\.gz/i ) { &gzip( $rStr0 => \$rstrOut ) or Carp::confess "-E-cannot zip strings into $fname; $GzipError"; }
    elsif ( $suffix =~ /\.bz2/i ) { &bzip2( $rStr0 => \$rstrOut,  BlockSize100K => 9 ) or Carp::confess "-E-cannot zip strings in $fname; $Bzip2Error"; }
    else                          { $rstrOut = $$rStr0; }
    my $fb1 = path($fname);
    $fb1->spew_raw($rstrOut);
    my $absF = abs_path($fname);
    printf "-i-wrote into %8s\n", &CONm( $absF, 9 );

    return $absF;

}    ## --- end sub write_file_zip}}


# sub-routine aliasing
*readZF  = \&read_text_zipped_okay;
*writeZF = \&write_file_zip;

1;
__END__

=head1 NAME

ZipSlurp - Extending File::Slurp onto gzip'ed and bzip2'ed files

File::Slurp cannot be applied directly onto
gzip/bzip2'ed files, and here is an easy way
to return content strings from zipped files.


=head1 SYNOPSIS

=head2 function &read_text_zipped_okay() and &readZF()

    use ZipSlurp;
    use feature qw/say/;

    my $s1;
    read_text_zipped_okay( "20ww35d311_b2f52300.txt.bz2", \$s1 );
    say $s1;
    my $s2;
    readZF( "20ww35d311_b2f52300.txt.bz2", \$s2 );
    say $s2;

One reason for it to pass around the reference-to-scalar
($s1 in this example) is __NOT__ to copy the string when
returning from sub-routine. A large file content (e.g.,
300MB) stored into a string will be costly to copy around.
Thus we are using C<\$s1> to I<receive> the file content.

C<&readZF()> is just an alias of function C<&read_text_zipped_okay()>.

=head3 DESCRIPTION

please provide two arguments; 


=over
    
=item  first argument is the I<filename>, 

=item     the second is the I<reference> toward a string scalar;

=back


=head2 function &write_file_zip() and &writeZF()

    use ZipSlurp;
    use feature qw/say/;

    my $s2a = qq(some random text);       # will save $s2a into "file1.txt"
    write_file_zip( \$s2a, "file1.txt" ); # 

    my $s2b = qq(more random text); # when no filename provided, will save into  
    writeZF( \$s2b );               # a randomly named file, like 20ww35d311_af5649d9.txt.bz2

Assuming we will write a very long string into a zipped
file, we would avoid the effort to copy the string content.
Thus this function is to take in the reference-to-scalar
and directly write to the (could be zipped) file.

When filename is missing, this function will generate a
random bzipped filename (I<20ww35d311_af5649d9.txt.bz2>
in above case) in __CURRENT__ directory.


=head1 security note

=over

=item C<File::Slurp> has some security issue on utf-8 and utf-16

=item For details, please see perl5.26+ C<File::Slurper>         

=item this ZipSlurp module uses C<Path::Tiny> module 
         for faster runtime and higher security

=back


=head1 SEE ALSO

=over

=item   File::Slurp

=item   File::Slurper

=item   Path::Tiny

=item IO::Uncompress::Bunzip2

=item IO::Uncompress::Gunzip 

=item IO::Compress::Gzip 

=item IO::Compress::Bzip2 

=back



